package sk.bielyvlk.vlkgps;

import java.text.DecimalFormat;

import android.location.Location;
import android.util.Log;

public class Coordinates {
	
	public static final double MAX_W =  -180.0;
	public static final double MAX_E =   180.0;
	public static final double MAX_S =   -90.0;
	public static final double MAX_N =    90.0;
	public static final double LON_LEN = 360.0;
	public static final double LAT_LEN = 180.0;
	
	/** circle around earth at latitude 0 */
	public static final double CLON = 40075017.0;
	/** polar distance around earth */
	public static final double PLAT = 20003932.0;
	/** arithmetical mean radius of earth in meters*/
	public static final double REARTH =  6378137.0;
	
	protected double longitude, latitude;
	
	public Coordinates() { set(0.0, 0.0); }
	public Coordinates(Coordinates coords) { set(coords); }
	public Coordinates(Location location) { set(location); }
	public Coordinates(double longitude, double latitude) { set(longitude, latitude); }
	public Coordinates(String longitude, String latitude) { set(longitude, latitude); }
	
	public double getLongitude() { return longitude; }
	public double getLatitude() { return latitude; }
	
	public static final int TYPE_LONGITUDE = 0;
	public static final int TYPE_LATITUDE = 1;
	
	public static final int FORMAT_DEGREES = 0;
	public static final int FORMAT_MINUTES = 1;
	public static final int FORMAT_SECONDS = 2;
	public static final int FORMAT_NMEA = 3;

	public static String convert(double coordinate, int format, int type) {
		StringBuilder out = new StringBuilder();
		
		boolean negative = false;
		if (coordinate < 0) {
			negative = true;
			coordinate -= coordinate;
		}
		
		if (format == FORMAT_DEGREES) {
			out.append(new DecimalFormat("00.000000").format(coordinate));
			out.append("°");
		} else if ((format == FORMAT_MINUTES) || (format == FORMAT_SECONDS)) {
			int degree = (int)coordinate;
			out.append(new DecimalFormat("00").format(degree));
			out.append("°");
			coordinate -= degree;
			coordinate *= 60.0;
			if (format == FORMAT_SECONDS) {
				int minutes = (int)coordinate;
				out.append(new DecimalFormat("00").format(minutes));
				out.append("'");
				coordinate -= minutes;
				coordinate *= 60.0;
				out.append(new DecimalFormat("00.00").format(coordinate));
				out.append("\"");
			} else {
				out.append(new DecimalFormat("00.0000").format(coordinate));
				out.append("'");
			}
		} else {
			out.append("unknown format");
		}

		switch (type) {
			case TYPE_LATITUDE :
				out.append((negative) ? 'S' : 'N');
				break;
			case TYPE_LONGITUDE :
				out.append((negative) ? 'W' : 'E');
				break;
		}
		return out.toString();
	}
	
	public String convertLongitude(int format) {
		return convert(longitude, format, TYPE_LONGITUDE);
	}
	
	public String convertLatitude(int format) {
		return convert(latitude, format, TYPE_LATITUDE);
	}
	
	public String toString() {
		return "" + longitude + ", " + latitude;
	}
	
	public void add(Coordinates coords) {
		longitude += coords.longitude;
		latitude += coords.latitude;
		align();
	}
	
	public void sub(Coordinates coords) {
		longitude -= coords.longitude;
		latitude -= coords.latitude;
		align();
	}
	
	public Coordinates add2(Coordinates coords) {
		return new Coordinates(longitude + coords.longitude, latitude + coords.latitude);
	}
	
	public Coordinates sub2(Coordinates coords) {
		return new Coordinates(longitude - coords.longitude, latitude - coords.latitude);
	}
	
	public void set(Coordinates coords) {
		longitude = coords.longitude;
		latitude = coords.latitude;
	}
	
	public void set(Location location) {
		longitude = location.getLongitude();
		latitude = location.getLatitude();
	}
	
	public void set(double longitude, double latitude) {
		this.longitude = longitude;
		this.latitude = latitude;
		align();
	}
	
	public void set(String longitude, String latitude) {
		set(Double.parseDouble(longitude), Double.parseDouble(latitude));
	}
	
	public void align() {
		if (latitude > 90) latitude = 90;
		if (latitude < -90) latitude = -90;
		while (longitude > 180) longitude -= 360;
		while (longitude <= -180) longitude += 360;
	}
	
	public double greatCircle(Coordinates coords) {
		return 2 * Math.asin(Math.sqrt(sqr(Math.sin((latitude - coords.latitude) / 2)) + Math.cos(latitude) * Math.cos(coords.latitude) * sqr(Math.sin((longitude - coords.longitude) / 2))));
	}
	
	public double getDistance(Coordinates coords) {
		return greatCircle(coords) * CLON / LON_LEN;
	}
	
	public double getBearing(Coordinates coords, double gc) {
		try {
			if (Math.abs(latitude) > MAX_N - 0.01) return 0.0;
			if (longitude == coords.longitude) return ((coords.latitude - latitude) >= 0.0) ? 0.0 : 180.0;
			if (gc <= Math.abs(coords.latitude - latitude)) {
				Log.w("getBearing", "Fixing greatCircle!!!");
				gc = Math.abs(coords.latitude - latitude) + 0.000001;   // FIX
			}
			double a = Math.acos((Math.sin(coords.latitude) - Math.sin(latitude) * Math.cos(gc)) / (Math.sin(gc) * Math.cos(latitude)));
			return (Math.sin(coords.longitude - longitude) < 0 ) ? LON_LEN - a : a;       // WEST or EAST
		} catch( Exception e ) {
			Log.e("getBearing", e.toString());
			return 0;
		}
	}
	
	public static double sqr(double a) { return a * a; }

}
